fix(plugins): add argv-level injection validation to build_command#280
fix(plugins): add argv-level injection validation to build_command#280advikdivekar wants to merge 2 commits into
Conversation
User-supplied scan inputs were interpolated into argv lists without validation. With asyncio.create_subprocess_exec (no shell=True), shell metacharacters are safe but argv-level flag injection is real: a value like ports="--script=evil.nse" becomes a standalone argv element that nmap accepts as a flag. Changes: - plugins.py: add _reject_injected_args() — rejects values that begin with '-' (flag injection); for port fields enforces digits/commas/hyphens only via _PORT_SPEC_PATTERN - plugins.py: add _validate_inputs_against_schema() — enforces SELECT allowed-values, INTEGER type, BOOLEAN type, and field-level regex patterns before any interpolation occurs; called in build_command() BEFORE _normalize_inputs() so SELECT checks run on raw user values - port_scanner.py: add _resolve_scan_type() and _resolve_ports() to map shorthand inputs (e.g. "-sV", "top100") to schema-compliant values; removes the old 'speed' field that had no corresponding plugin field - test_command_injection.py: 52 tests covering injection rejection, SELECT/INTEGER/BOOLEAN validation, pattern validation, port normalisation, scan-type normalisation Fixes utksh1#201 (command argument injection via unsanitized scan_type/ports/speed).
|
@utksh1 please review it and I have asked to claim few issues, please assign those issues to me under GSSoC 2026 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3f1a998082
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if raw == "top1000": | ||
| return "1-1000" |
There was a problem hiding this comment.
Preserve top1000 semantics when normalizing ports
Mapping "top1000" to "1-1000" changes scan behavior from “top 1000 most common ports” to “first 1000 numeric ports,” which can miss frequently exposed services above 1000 and produce materially different findings for existing callers that use this shortcut. This should preserve the original top-ports behavior (e.g., via preset/flag handling) rather than narrowing the scan range.
Useful? React with 👍 / 👎.
| "service_detection": bool(inputs.get("service_detection", True)), | ||
| "os_detection": bool(inputs.get("os_detection", False)), | ||
| "safe_mode": bool(inputs.get("safe_mode", True)), |
There was a problem hiding this comment.
Parse boolean strings explicitly in scanner inputs
Using bool(...) here inverts common string inputs because any non-empty string is truthy, so values like "false" will still enable service_detection, os_detection, and safe_mode. Since task inputs are passed through as arbitrary JSON, clients that send booleans as strings will get incorrect scan flags and behavior. These fields need explicit boolean parsing (e.g., recognize "true"/"false", "1"/"0") instead of Python truthiness coercion.
Useful? React with 👍 / 👎.
What is the problem
Closes #201
PluginManager.build_command()interpolated user-supplied values directly into the argv list without any validation. Whileasyncio.create_subprocess_exec(noshell=True) prevents shell metacharacter injection, argv-level flag injection is still exploitable:Affected fields in the nmap plugin:
ports(free-text string, no validation), and any plugin field that accepts a STRING type without a pattern constraint.What was changed
backend/secuscan/plugins.py_PORT_SPEC_PATTERN— regex for valid port specs;_reject_injected_args()— rejects values beginning with-;_validate_inputs_against_schema()— enforces SELECT allowed-values, INTEGER type, BOOLEAN type, and field-level regex patterns;build_command()— calls validation before_normalize_inputs()backend/secuscan/scanners/port_scanner.py_resolve_scan_type()— maps shorthand values (e.g.-sV,sT) to schema-compliant SELECT values (S/T/U);_resolve_ports()— maps shorthand (top100,all) to numeric ranges accepted by the plugin; removes unusedspeedfieldtesting/backend/unit/test_command_injection.pyWhy this approach
metadata.json; validating against it blocks injections for all plugins generically, not just nmap^[\d,\-]+$) because they legitimately contain hyphens (ranges), so a blanket "no leading dash" rule is insufficientcreate_subprocess_execalready handles shell metacharacters; this fix closes the remaining argv-level vectorHow to test
Edge cases covered
--top-ports 100default)"1-1000"are valid (hyphen in numeric context)"-sT"and"sT"are normalised to"T"by PortScanner before reaching validation"30"accepted,"thirty"rejected,"--evil"rejectedTrue,"true","1"accepted;"yes"rejectedNoneand""values are skipped (handled by_with_field_defaults)Verification checklist
pytest testing/backend/— 290 passed (1 pre-existing failure intest_route_rejects_task_when_limiter_full)